package com.allin.mall.common.redis.aspect;

import cn.hutool.core.lang.UUID;
import com.allin.mall.common.core.api.ResultCodeEnum;
import com.allin.mall.common.core.exception.BusinessException;
import com.allin.mall.common.redis.annotation.DistributedLock;
import com.allin.mall.common.redis.service.RedisDistributedLock;
import jakarta.annotation.Resource;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.Ordered;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

@Aspect
public class DistributedLockAspect implements Ordered {

    private final SpelExpressionParser parser = new SpelExpressionParser();
    private final ParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();

    @Resource
    private RedisDistributedLock lock;

    @Pointcut("@annotation(com.allin.mall.common.redis.annotation.DistributedLock)")
    public void lockMethod() {
    }

    @Around(value = "lockMethod()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        DistributedLock distributedLock = method.getAnnotation(DistributedLock.class);
        if (distributedLock == null) {
            return point.proceed();
        }
        // 通过el表达式解析key
        EvaluationContext ctx = new MethodBasedEvaluationContext(null, method, point.getArgs(), nameDiscoverer);
        final String key = distributedLock.key() == null || distributedLock.key().isEmpty() ? distributedLock.keyPrefix() : String.format("%s:%s", distributedLock.keyPrefix(), parser.parseExpression(distributedLock.key()).getValue(ctx, String.class));

        long wait = TimeUnit.SECONDS.toMillis(distributedLock.waitTime());
        long start = System.currentTimeMillis();
        while (System.currentTimeMillis() - start < wait) {
            if (!lock.tryLock(key, TimeUnit.SECONDS, wait)) {
                TimeUnit.MILLISECONDS.sleep(50);
                continue;
            }
            try {
                return point.proceed();
            } finally {
                lock.unlock(key);
            }
        }
        throw new BusinessException(ResultCodeEnum.TRY_LOCK_TIMEOUT);
    }

    @Override
    public int getOrder() {
        return -1;
    }
}